/**
 * Copyright (c) 2009 Collaborative Development Group, C.S. Dept., University of Bari
 *
 * All rights reserved. This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License v1.0  which accompanies this distribution, and is 
 * available at http://www.eclipse.org/legal/epl-v10.html
 */
package it.uniba.di.cdg.penelope.ui.swt;

import static org.osgi.service.log.LogService.LOG_DEBUG;
import static org.osgi.service.log.LogService.LOG_ERROR;
import static org.osgi.service.log.LogService.LOG_WARNING;

import java.lang.reflect.Field;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.eclipse.swt.widgets.Display;
import org.osgi.service.log.LogService;

import com.google.inject.Inject;
import com.google.inject.Provider;

/**
 * Provides thread safety for methods that are executed from different threads.
 */
public class SWTThreadInterceptor implements MethodInterceptor {

	@Inject
	private Provider<Display> displayProvider;
	
	@Inject 
	private LogService logger;
	
	/**
	 * Runnable that holds values that can be checked back after execution.
	 */
	private static abstract class SynchRunnable implements Runnable {
	    
		private Object value;

	    public Object getValue() {
	        return value;
	    }

	    public void setValue( Object value ) {
	        this.value = value;
	    }
	}
	
	
	/* (non-Javadoc)
	 * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
	 */
	@Override
	public Object invoke( final MethodInvocation invocation ) throws Throwable {
		logger.log( LOG_DEBUG, "Intercepting method: " + invocation.getMethod() + " of type " + invocation.getThis().getClass() );

		Display display = displayProvider.get();
	
		if (invocation.getMethod().getAnnotation( SWTSyncExec.class ) != null ) {
			Object result = executeSynch( display, invocation );
			logger.log( LOG_DEBUG, "End SYNCH Intercepting method!" );
			return result;
		} else { // ASYNCH
			// This is required to avoid recursive invocation (see #18)
			Field field = invocation.getClass().getDeclaredField( "index" );
			try {
				field.setAccessible( true );
				field.setInt( invocation, field.getInt( invocation ) + 1 );
			} finally {
				field.setAccessible( false );
			}

			executeAsynch( display, invocation );
			// Asynch execution return void by definition!
			logger.log( LOG_DEBUG, "End ASYNCH Intercepting method!" );
			
			return null;
		}
	}
	
	private void executeAsynch( Display display, final MethodInvocation invocation ) {
		display.asyncExec( new Runnable() {
            public void run() {
                try {
                    invocation.proceed();
                } catch (Throwable e) {
                	logger.log( LOG_ERROR, e.getMessage() );
                	e.printStackTrace();
                    throw new SWTThreadException( e );
                }
            }
        });
	}
	
	private Object executeSynch( Display display, final MethodInvocation invocation ) {
    	final SynchRunnable task = new SynchRunnable() {
            public void run() {
                try {
                	Object value = invocation.proceed();
                    setValue( value );
                } catch (Throwable e) {
                	logger.log( LOG_ERROR, e.getMessage() );
                	e.printStackTrace();
                    throw new SWTThreadException( e );
                } 
            }
    	};
        display.syncExec( task );
        return task.getValue();		
	}
}
